/*
** Haaf's Game Engine 1.8
** Copyright (C) 2003-2007, Relish Games
** hge.relishgames.com
**
** Core system functions
*/


#include "hge_impl.h"


#define LOWORDINT(n) ((int)((signed short)(LOWORD(n))))
#define HIWORDINT(n) ((int)((signed short)(HIWORD(n))))


const HGE_CHAR *WINDOW_CLASS_NAME = TXT("HGE__WNDCLASS");

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);


int			nRef=0;
HGE_Impl*	pHGE=0;



BOOL APIENTRY DllMain(HANDLE, DWORD, LPVOID)
{
    return TRUE;
}


HGE* CALL hgeCreate(int ver)
{
	if(ver==HGE_VERSION)
		return (HGE*)HGE_Impl::_Interface_Get();
	else
		return 0;
}


HGE_Impl* HGE_Impl::_Interface_Get()
{
	if(!pHGE) pHGE=new HGE_Impl();

	nRef++;

	return pHGE;
}


void CALL HGE_Impl::Release()
{
	nRef--;

	if(!nRef)
	{
		if(pHGE->hwnd) pHGE->System_Shutdown();
		Resource_RemoveAllPacks();
		delete pHGE;
		pHGE=0;
	}
}


bool CALL HGE_Impl::System_Initiate()
{
	OSVERSIONINFO	os_ver;
	SYSTEMTIME		tm;
	MEMORYSTATUS	mem_st;
	WNDCLASS		winclass;
	int				width, height;

	// Log system info

	System_Log(TXT("HGE Started..\n"));

	System_Log(TXT("HGE version: %X.%X"), HGE_VERSION>>8, HGE_VERSION & 0xFF);
	GetLocalTime(&tm);
	System_Log(TXT("Date: %02d.%02d.%d, %02d:%02d:%02d\n"), tm.wDay, tm.wMonth, tm.wYear, tm.wHour, tm.wMinute, tm.wSecond);

	System_Log(TXT("Application: %s"),szWinTitle);
	os_ver.dwOSVersionInfoSize = sizeof(os_ver);
	GetVersionEx(&os_ver);
	System_Log(TXT("OS: Windows %ld.%ld.%ld"),os_ver.dwMajorVersion,os_ver.dwMinorVersion,os_ver.dwBuildNumber);

	GlobalMemoryStatus(&mem_st);
	System_Log(TXT("Memory: %ldK total, %ldK free\n"), mem_st.dwTotalPhys/1024L,mem_st.dwAvailPhys/1024L);

	// Register window class
	
	winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winclass.lpfnWndProc	= WindowProc;
	winclass.cbClsExtra		= 0;
	winclass.cbWndExtra		= 0;
	winclass.hInstance		= hInstance;
	winclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	winclass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	winclass.lpszMenuName	= NULL; 
	winclass.lpszClassName	= WINDOW_CLASS_NAME;
	if(szIcon) winclass.hIcon = LoadIcon(hInstance, szIcon);
	else winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	
	if (!RegisterClass(&winclass)) {
		_PostError(TXT("Can't register window class"));
		return false;
	}

	// Create window

	width=nScreenWidth + GetSystemMetrics(SM_CXFIXEDFRAME)*2;
	height=nScreenHeight + GetSystemMetrics(SM_CYFIXEDFRAME)*2 + GetSystemMetrics(SM_CYCAPTION);

	rectW.left=(GetSystemMetrics(SM_CXSCREEN)-width)/2;
	rectW.top=(GetSystemMetrics(SM_CYSCREEN)-height)/2;
	rectW.right=rectW.left+width;
	rectW.bottom=rectW.top+height;
	styleW=WS_POPUP|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_VISIBLE; //WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX;

	rectFS.left=0;
	rectFS.top=0;
	rectFS.right=nScreenWidth;
	rectFS.bottom=nScreenHeight;
	styleFS=WS_POPUP|WS_VISIBLE; //WS_POPUP

	if(hwndParent)
	{
		rectW.left=0;
		rectW.top=0;
		rectW.right=nScreenWidth;
		rectW.bottom=nScreenHeight;
		styleW=WS_CHILD|WS_VISIBLE; 
		bWindowed=true;
	}

	if(bWindowed)
		hwnd = CreateWindowEx(0, WINDOW_CLASS_NAME, szWinTitle, styleW,
				rectW.left, rectW.top, rectW.right-rectW.left, rectW.bottom-rectW.top,
				hwndParent, NULL, hInstance, NULL);
	else
		hwnd = CreateWindowEx(WS_EX_TOPMOST, WINDOW_CLASS_NAME, szWinTitle, styleFS,
				0, 0, 0, 0,
				NULL, NULL, hInstance, NULL);
	if (!hwnd)
	{
		_PostError(TXT("Can't create window"));
		return false;
	}

	ShowWindow(hwnd, SW_SHOW);

	// Init subsystems

	timeBeginPeriod(1);
	Random_Seed();
	_InitPowerStatus();
	_InputInit();
	if(!_GfxInit()) { System_Shutdown(); return false; }
	if(!_SoundInit()) { System_Shutdown(); return false; }

	System_Log(TXT("Init done.\n"));

	fTime=0.0f;
	t0=t0fps=timeGetTime();
	dt=cfps=0;
	nFPS=0;

	// Show splash

#ifdef DEMO

	bool			(*func)();
	bool			(*rfunc)();
	HWND			hwndTmp;

	if(pHGE->bDMO)
	{
		Sleep(200);
		func=(bool(*)())pHGE->System_GetStateFunc(HGE_FRAMEFUNC);
		rfunc=(bool(*)())pHGE->System_GetStateFunc(HGE_RENDERFUNC);
		hwndTmp=hwndParent; hwndParent=0;
		pHGE->System_SetStateFunc(HGE_FRAMEFUNC, DFrame);
		pHGE->System_SetStateFunc(HGE_RENDERFUNC, 0);
		DInit();
		pHGE->System_Start();
		DDone();
		hwndParent=hwndTmp;
		pHGE->System_SetStateFunc(HGE_FRAMEFUNC, func);
		pHGE->System_SetStateFunc(HGE_RENDERFUNC, rfunc);
	}

#endif

	// Done

	return true;
}

void CALL HGE_Impl::System_Shutdown()
{
	System_Log(TXT("\nFinishing.."));

	timeEndPeriod(1);
	if(hSearch) { FindClose(hSearch); hSearch=0; }
	_ClearQueue();
	_SoundDone();
	_GfxDone();
	_DonePowerStatus();

	if(hwnd)
	{
		//ShowWindow(hwnd, SW_HIDE);
		//SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_TOOLWINDOW);
		//ShowWindow(hwnd, SW_SHOW);
		DestroyWindow(hwnd);
		hwnd=0;
	}

	if(hInstance) UnregisterClass(WINDOW_CLASS_NAME, hInstance);

	System_Log(TXT("The End."));
}


bool CALL HGE_Impl::System_Start()
{
#ifdef HGE_STATEMACHINE
	updateModalCount = 0;
	bool res = (System_ShowModal(0) > 0);
	_ClearQueue();
	bActive=false;
	return res;
#else
	MSG		msg;

	if(!hwnd)
	{
		_PostError(TXT("System_Start: System_Initiate wasn't called"));
		return false;
	}

	if(!procFrameFunc) {
		_PostError(TXT("System_Start: No frame function defined"));
		return false;
	}

	bActive=true;

	// MAIN LOOP

	for(;;)
	{
		
		// Process window messages if not in "child mode"
		// (if in "child mode" the parent application will do this for us)

		if(!hwndParent)
		{
			if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
			{ 
				if (msg.message == WM_QUIT)	break;
				// TranslateMessage(&msg);
				DispatchMessage(&msg);
				continue;
			}
		}

		// Check if mouse is over HGE window for Input_IsMouseOver

		_UpdateMouse();

		// If HGE window is focused or we have the "don't suspend" state - process the main loop

		if(bActive || bDontSuspend)
		{
			// Ensure we have at least 1ms time step
			// to not confuse user's code with 0

			do { dt=timeGetTime() - t0; } while(dt < 1);

			// If we reached the time for the next frame
			// or we just run in unlimited FPS mode, then
			// do the stuff

			if(dt >= nFixedDelta)
			{
				// fDeltaTime = time step in seconds returned by Timer_GetDelta

				fDeltaTime=dt/1000.0f;

				// Cap too large time steps usually caused by lost focus to avoid jerks

				if(fDeltaTime > 0.2f)
				{
					fDeltaTime = nFixedDelta ? nFixedDelta/1000.0f : 0.01f;
				}

				// Update time counter returned Timer_GetTime

				fTime += fDeltaTime;

				// Store current time for the next frame
				// and count FPS

				t0=timeGetTime();
				if(t0-t0fps <= 1000) cfps++;
				else {
					nFPS=cfps; cfps=0; t0fps=t0;
					_UpdatePowerStatus();
				}

				// Do user's stuff

				if(procFrameFunc()) break;
				if(procRenderFunc) procRenderFunc();
				
				// If if "child mode" - return after processing single frame

				if(hwndParent) break;

				// Clean up input events that were generated by
				// WindowProc and weren't handled by user's code

				_ClearQueue();

				// If we use VSYNC - we could afford a little
				// sleep to lower CPU usage

				// if(!bWindowed && nHGEFPS==HGEFPS_VSYNC) Sleep(1);
			}

			// If we have a fixed frame rate and the time
			// for the next frame isn't too close, sleep a bit

			else
			{
				if(nFixedDelta && dt+3 < nFixedDelta) Sleep(1);
			}
		}

		// If main loop is suspended - just sleep a bit
		// (though not too much to allow instant window
		// redraw if requested by OS)

		else Sleep(1);
	}

	_ClearQueue();

	bActive=false;

	return true;
#endif
}

#ifdef HGE_STATEMACHINE
void CALL HGE_Impl::System_Show(HGEAPP *app, bool render)
{
	if(!app) return;
	Apps->AddFirst(app);
	app->RenderModal = render;
	if(render) RenderModal = app;
	app->OnActive();
	app->OnShow();
}

int CALL HGE_Impl::System_ShowModal(HGEAPP *app, bool render)
{
	MSG msg;
	if(!hwnd) {
		_PostError(TXT("System_Start: System_Initiate wasn't called"));
		return -1;
	}

	updateModalCount++;
	System_Log(TXT("Begin modal #%d"), updateModalCount);

	bActive = true;

	// MAIN LOOP

	if(app) {
		System_Show(app, render);
		app->UpdateModal = true;
	}

	for(;;) {
		if(isExit) {
			System_Log(TXT("End modal #%d"), updateModalCount);
			updateModalCount--;
			return 1;
		}
		if(!hwndParent) {
			if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { 
				if(msg.message == WM_QUIT) {
					isExit = true;
					System_Log(TXT("End modal #%d"), updateModalCount);
					updateModalCount--;
					return 1;
				}
				// TranslateMessage(&msg);
				DispatchMessage(&msg);
				continue;
			}
		}

		_UpdateMouse();

		if(bActive || bDontSuspend) {
			do { dt=timeGetTime() - t0; } while(dt < 1);

			if(dt >= nFixedDelta) {
				fDeltaTime=dt/1000.0f;
				if(fDeltaTime > 0.2f) {
					fDeltaTime = nFixedDelta ? nFixedDelta/1000.0f : 0.01f;
				}
				fTime += fDeltaTime;
				t0=timeGetTime();
				if(t0-t0fps <= 1000) cfps++;
				else {
					nFPS=cfps; cfps=0; t0fps=t0;
					_UpdatePowerStatus();
				}

				if(procFrameFunc && procFrameFunc()) {
					System_Log(TXT("End modal #%d"), updateModalCount);
					updateModalCount--;
					return 1;
				}
				
				// update
				int res = 0;
				Apps->Begin();
				HGEAPP *a = (HGEAPP*)Apps->Next();
				bool upd = true;
				bool ret = true;
				if(!app) ret = false;
				while(a) {
					if(upd) {
						res = a->Update(fDeltaTime);
						if(res < 0) {
							System_Log(TXT("End modal #%d"), updateModalCount);
							updateModalCount--;
							return res;
						}
					}
					if(a == app) {
						upd = false;
						ret = false;
					}
					if(res > 0) {
						//if(a == app) System_Hide(a);
						System_Log(TXT("End modal #%d"), updateModalCount);
						updateModalCount--;
						return res;
					}
					a = (HGEAPP*)Apps->Next();
				}
				if(ret) isExit = true;
				// render
				Gfx_BeginScene();
				System_Render();
				if(procRenderFunc) procRenderFunc();
				Gfx_EndScene();



				if(hwndParent) {
					System_Log(TXT("End modal #%d"), updateModalCount);
					updateModalCount--;
					return 1;
				}
				_ClearQueue();
			} else
			{
				if(nFixedDelta && dt+3 < nFixedDelta) Sleep(1);
			}
		} else Sleep(1);
	}
	_ClearQueue();
	bActive=false;
	System_Log(TXT("End modal #%d"), updateModalCount);
	updateModalCount--;
	return 1;
}

void CALL HGE_Impl::System_Hide(HGEAPP *app)
{
	if(!app) return;
	app->OnHide();
	if(app->RenderModal) {
		RenderModal = 0;
		app->RenderModal = false;
	}
	if(app->UpdateModal) {
		Apps->SetCurr(app);
		Apps->Next();
		HGEAPP *a = (HGEAPP*)Apps->Next();
		while(a) {
			a->OnActive();
			if(a->RenderModal) RenderModal = a;
			a = (HGEAPP*)Apps->Next();
		}
	}
	Apps->Remove(app);
	app->UpdateModal = false;
}

void CALL HGE_Impl::System_Clear(void)
{
	Apps->Begin();
	HGEAPP *a = (HGEAPP*)Apps->Next();
	while(a) {
		a->OnHide();
		a->RenderModal = false;
		a->UpdateModal = false;
		a = (HGEAPP*)Apps->Next();
	}

	Apps->Clear();
	RenderModal = 0;
}

void CALL HGE_Impl::System_Render(void) {
	Apps->End();
	HGEAPP *a = (HGEAPP*)Apps->Prev();
	bool rndr = (RenderModal == 0);
	while(a) {
		if(!rndr && a->RenderModal && RenderModal == a) rndr = true;
		if(rndr) {
			a->Render();
		}
		a = (HGEAPP*)Apps->Prev();
	}
}
#endif

void CALL HGE_Impl::System_SetStateBool(hgeBoolState state, bool value)
{
	switch(state)
	{
		case HGE_WINDOWED:		if(VertArray || hwndParent) break;
								if(pD3DDevice && bWindowed != value)
								{
									if(d3dppW.BackBufferFormat==D3DFMT_UNKNOWN || d3dppFS.BackBufferFormat==D3DFMT_UNKNOWN) break;

									if(bWindowed) GetWindowRect(hwnd, &rectW);
									bWindowed=value;
									if(bWindowed) d3dpp=&d3dppW;
									else d3dpp=&d3dppFS;

									if(_format_id(d3dpp->BackBufferFormat) < 4) nScreenBPP=16;
									else nScreenBPP=32;

									_GfxRestore();
									_AdjustWindow();
								}
								else bWindowed=value;
								break;

		case HGE_ZBUFFER:		if(!pD3DDevice)	bZBuffer=value;
								break;

		case HGE_TEXTUREFILTER: bTextureFilter=value;
								if(pD3DDevice)
								{
									_render_batch();
									if(bTextureFilter)
									{
										pD3DDevice->SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_LINEAR);
										pD3DDevice->SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_LINEAR);
									}
									else
									{
										pD3DDevice->SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_POINT);
										pD3DDevice->SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_POINT);
									}
								}
								break;

		case HGE_USESOUND:		if(bUseSound!=value)
								{
									bUseSound=value;
									if(bUseSound && hwnd) _SoundInit();
									if(!bUseSound && hwnd) _SoundDone();
								}
								break;

		case HGE_HIDEMOUSE:		bHideMouse=value; break;

		case HGE_UPDATEMOUSE: 
			bUpdateMouse = value; 
			if(bActive && bHideMouse) SetCursor(NULL);
			else SetCursor(LoadCursor(NULL, IDC_ARROW));
			break;

		case HGE_DONTSUSPEND:	bDontSuspend=value; break;

		#ifdef DEMO
		case HGE_SHOWSPLASH:	bDMO=value; break;
		#endif
	}
}

void CALL HGE_Impl::System_SetStateFunc(hgeFuncState state, hgeCallback value)
{
	switch(state)
	{
		case HGE_FRAMEFUNC:		 procFrameFunc=value; break;
		case HGE_RENDERFUNC:	 procRenderFunc=value; break;
		case HGE_FOCUSLOSTFUNC:	 procFocusLostFunc=value; break;
		case HGE_FOCUSGAINFUNC:	 procFocusGainFunc=value; break;
		case HGE_GFXRESTOREFUNC: procGfxRestoreFunc=value; break;
		case HGE_EXITFUNC:		 procExitFunc=value; break;
	}
}

void CALL HGE_Impl::System_SetStateHwnd(hgeHwndState state, HWND value)
{
	switch(state)
	{
		case HGE_HWNDPARENT:	if(!hwnd) hwndParent=value; break;
	}
}

void CALL HGE_Impl::System_SetStateInt(hgeIntState state, int value)
{
	switch(state)
	{
		case HGE_SCREENWIDTH:	if(!pD3DDevice) nScreenWidth=value; break;

		case HGE_SCREENHEIGHT:	if(!pD3DDevice) nScreenHeight=value; break;

		case HGE_SCREENBPP:		if(!pD3DDevice) nScreenBPP=value; break;

		case HGE_SAMPLERATE:	if(!hBass) nSampleRate=value;
								break;

		case HGE_FXVOLUME:		nFXVolume=value;
								_SetFXVolume(nFXVolume);
								break;

		case HGE_MUSVOLUME:		nMusVolume=value;
								_SetMusVolume(nMusVolume);
								break;

		case HGE_STREAMVOLUME:	nStreamVolume=value;
								_SetStreamVolume(nStreamVolume);
								break;

		case HGE_FPS:			if(VertArray) break;

								if(pD3DDevice)
								{
									if((nHGEFPS>=0 && value <0) || (nHGEFPS<0 && value>=0))
									{
										if(value==HGEFPS_VSYNC)
										{
											d3dppW.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC;
											d3dppFS.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE;
										}
										else
										{
											d3dppW.SwapEffect = D3DSWAPEFFECT_COPY;
											d3dppFS.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
										}
										//if(procFocusLostFunc) procFocusLostFunc();
										_GfxRestore();
										//if(procFocusGainFunc) procFocusGainFunc();
									}
								}
								nHGEFPS=value;
								if(nHGEFPS>0) nFixedDelta=int(1000.0f/value);
								else nFixedDelta=0;
								break;
	}
}

void CALL HGE_Impl::System_SetStateString(hgeStringState state, const HGE_CHAR *value)
{
	FILE *hf;
	
	switch(state)
	{
		case HGE_ICON:			szIcon=value;
								if(pHGE->hwnd) SetClassLong(pHGE->hwnd, GCL_HICON, (LONG)LoadIcon(pHGE->hInstance, szIcon));
								break;
		case HGE_TITLE:			HGE_SCPY(szWinTitle,value);
								if(pHGE->hwnd) SetWindowText(pHGE->hwnd, szWinTitle);
								break;
		case HGE_INIFILE:		if(value) HGE_SCPY(szIniFile,Resource_MakePath(value));
								else szIniFile[0]=0;
								break;
		case HGE_LOGFILE:		if(value)
								{
									HGE_SCPY(szLogFile,Resource_MakePath(value));
									hf=HGE_FOPEN_W(szLogFile);
									if(!hf) szLogFile[0]=0;
									else {
#ifdef UNICODE
										unsigned short signature = 0xFEFF;
										fwrite(&signature, 2, 1, hf);
#endif
										fclose(hf);
									}
								}
								else szLogFile[0]=0;
								break;
	}
}

void CALL HGE_Impl::System_SetStateVoid(hgeVoidState state, void *value)
{
	switch(state)
	{
		case HGE_USERDATA:	userdata=value; break;
	}
}

bool CALL HGE_Impl::System_GetStateBool(hgeBoolState state)
{
	switch(state)
	{
		case HGE_WINDOWED:		return bWindowed;
		case HGE_ZBUFFER:		return bZBuffer;
		case HGE_TEXTUREFILTER:	return bTextureFilter;
		case HGE_USESOUND:		return bUseSound;
		case HGE_DONTSUSPEND:	return bDontSuspend;
		case HGE_HIDEMOUSE:		return bHideMouse;
		case HGE_UPDATEMOUSE:	return bUpdateMouse;

		#ifdef DEMO
		case HGE_SHOWSPLASH:	return bDMO;
		#endif
	}

	return false;
}

hgeCallback CALL HGE_Impl::System_GetStateFunc(hgeFuncState state)
{
	switch(state)
	{
		case HGE_FRAMEFUNC:		return procFrameFunc;
		case HGE_RENDERFUNC:	return procRenderFunc;
		case HGE_FOCUSLOSTFUNC:	return procFocusLostFunc;
		case HGE_FOCUSGAINFUNC:	return procFocusGainFunc;
		case HGE_EXITFUNC:		return procExitFunc;
	}

	return NULL;
}

HWND CALL HGE_Impl::System_GetStateHwnd(hgeHwndState state)
{
	switch(state)
	{
		case HGE_HWND:			return hwnd;
		case HGE_HWNDPARENT:	return hwndParent;
	}

	return 0;
}

int CALL HGE_Impl::System_GetStateInt(hgeIntState state)
{
	switch(state)
	{
		case HGE_SCREENWIDTH:	return nScreenWidth;
		case HGE_SCREENHEIGHT:	return nScreenHeight;
		case HGE_SCREENBPP:		return nScreenBPP;
		case HGE_SAMPLERATE:	return nSampleRate;
		case HGE_FXVOLUME:		return nFXVolume;
		case HGE_MUSVOLUME:		return nMusVolume;
		case HGE_STREAMVOLUME:	return nStreamVolume;
		case HGE_FPS:			return nHGEFPS;
		case HGE_POWERSTATUS:	return nPowerStatus;
		//case HGE_USERDATA:	return (int)userdata;
	}

	return 0;
}

const HGE_CHAR* CALL HGE_Impl::System_GetStateString(hgeStringState state) {
	switch(state) {
		case HGE_ICON:			return szIcon;
		case HGE_TITLE:			return szWinTitle;
		case HGE_INIFILE:		if(szIniFile[0]) return szIniFile;
								else return 0;
		case HGE_LOGFILE:		if(szLogFile[0]) return szLogFile;
								else return 0;
	}

	return NULL;
}

void* CALL HGE_Impl::System_GetStateVoid(hgeVoidState state)
{
	switch(state)
	{
		case HGE_USERDATA:	return userdata;
	}

	return 0;
}

HGE_CHAR* CALL HGE_Impl::System_GetErrorMessage()
{
	return szError;
}

void CALL HGE_Impl::System_Log(const HGE_CHAR *szFormat, ...)
{
	FILE *hf = NULL;
	//va_list ap;
	
	if(!szLogFile[0]) return;

	//char Path[MAX_PATH];
	//W2C(szLogFile, Path, MAX_PATH-1);
	hf = HGE_FOPEN_AB(szLogFile);
	if(!hf) return;

	/*va_start(ap, szFormat);
	vfwprintf(hf, szFormat, ap);
	va_end(ap);*/
	
	HGE_CHAR buf[1024];
	int sz = sizeof(buf);
	char	*pArg=(char *) &szFormat+sizeof(szFormat);
	ZeroMemory(buf, 2048);
	HGE_VSNPRINTF(buf, 2047, szFormat, pArg);
	buf[1023] = TXT('\0');
	fwrite(buf, sizeof(HGE_CHAR), HGE_SLEN(buf), hf);

	HGE_FPRINTF(hf, TXT("\n"));

	fclose(hf);
}

bool CALL HGE_Impl::System_Launch(const HGE_CHAR *url)
{
	if((DWORD)ShellExecute(pHGE->hwnd, NULL, url, NULL, NULL, SW_SHOWMAXIMIZED)>32) return true;
	else return false;
}

void CALL HGE_Impl::System_Snapshot(const HGE_CHAR *filename, HTARGET targ)
{
	LPDIRECT3DSURFACE8 pSurf;
	HGE_CHAR *shotname, tempname[_MAX_PATH];
	CRenderTargetList *target=(CRenderTargetList *)targ;

	int i;

	if(!filename)
	{
		i=0;
		shotname=Resource_EnumFiles(TXT("shot???.bmp"));
		while(shotname)
		{
			i++;
			shotname=Resource_EnumFiles();
		}
		HGE_SPRINTF(tempname, TXT("shot%03d.bmp"), i);
		filename=Resource_MakePath(tempname);
	}

	if(pD3DDevice)
	{
		if(target)
		{
			target->pTex->GetSurfaceLevel(0, &pSurf);
		}
		else
		{
			pD3DDevice->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &pSurf);
		}
		
		D3DXSaveSurfaceToFile(filename, D3DXIFF_BMP, pSurf, NULL, NULL);
		pSurf->Release();
	}
}

//////// Implementation ////////


HGE_Impl::HGE_Impl()
{
	hInstance=GetModuleHandle(0);
	hwnd=0;
	bActive=false;
	szError[0]=0;

	pD3D=0;
	pD3DDevice=0;
	d3dpp=0;
	pTargets=0;
	pCurTarget=0;
	pScreenSurf=0;
	pScreenDepth=0;
	pVB=0;
	pIB=0;
	VertArray=0;
	textures=0;

	hBass=0;
	bSilent=false;
	streams=0;

	hSearch=0;
	res=0;

	queue=0;
	Char=VKey=Zpos=0;
	Xpos=Ypos=0.0f;
	bMouseOver=false;
	bCaptured=false;

	nHGEFPS=HGEFPS_UNLIMITED;
	fTime=0.0f;
	fDeltaTime=0.0f;
	nFPS=0;
	
	procFrameFunc=0;
	procRenderFunc=0;
	procFocusLostFunc=0;
	procFocusGainFunc=0;
	procGfxRestoreFunc=0;
	procExitFunc=0;
	szIcon=0;
	HGE_SCPY(szWinTitle,TXT("HGEUE"));
	nScreenWidth=800;
	nScreenHeight=600;
	nScreenBPP=32;
	bWindowed=false;
	bZBuffer=false;
	bTextureFilter=true;
	szLogFile[0]=0;
	szIniFile[0]=0;
	bUseSound=true;
	nSampleRate=44100;
	nFXVolume=100;
	nMusVolume=100;
	nStreamVolume=100;
	nFixedDelta=0;
	bHideMouse=true;
	bUpdateMouse = true;
	bDontSuspend=false;
	hwndParent=0;

	userdata = 0;
#ifdef HGE_STATEMACHINE
	Apps = new mhaList(false);
	RenderModal = 0;
#endif
	isExit = false;

	nPowerStatus=HGEPWR_UNSUPPORTED;
	hKrnl32 = NULL;
	lpfnGetSystemPowerStatus = NULL;

#ifdef DEMO
	bDMO=true;
#endif


	GetModuleFileName(GetModuleHandle(NULL), szAppPath, sizeof(szAppPath));
	int i;
	for(i=HGE_SLEN(szAppPath)-1; i>0; i--) if(szAppPath[i]==TXT('\\')) break;
	szAppPath[i+1]=0;
}

void HGE_Impl::_PostError(HGE_CHAR *error)
{
	System_Log(error);
	HGE_SCPY(szError,error);
}

void HGE_Impl::_FocusChange(bool bAct)
{
	bActive=bAct;

	if(bActive)
	{
		if(procFocusGainFunc) procFocusGainFunc();
#ifdef HGE_STATEMACHINE
		Apps->Begin();
		HGEAPP *a = (HGEAPP*)Apps->Next();
		while(a) {
			a->FocusGain();
			a = (HGEAPP*)Apps->Next();
		}
#endif
	}
	else
	{
		if(procFocusLostFunc) procFocusLostFunc();
#ifdef HGE_STATEMACHINE
		Apps->Begin();
		HGEAPP *a = (HGEAPP*)Apps->Next();
		while(a) {
			a->FocusLost();
			a = (HGEAPP*)Apps->Next();
		}
#endif
	}
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	bool bActivating;
#ifdef HGE_STATEMACHINE
	HGEAPP *a;
	bool res;
#endif

	switch(msg)
	{	
		case WM_CREATE: 
			return FALSE;
		
		case WM_PAINT:
#ifndef HGE_STATEMACHINE
			if(pHGE->pD3D && pHGE->procRenderFunc && pHGE->bWindowed) pHGE->procRenderFunc();
#else
			if(pHGE->pD3D && pHGE->bWindowed) {
				pHGE->System_Render();
				if(pHGE->procRenderFunc) pHGE->procRenderFunc();
			}
#endif
			break;

		case WM_DESTROY:
			PostQuitMessage(0);
			return FALSE;
/*
		case WM_ACTIVATEAPP:
			if(pHGE->pD3D && pHGE->bActive != (wparam == TRUE)) pHGE->_FocusChange(wparam == TRUE);
			return FALSE;
*/
		case WM_ACTIVATE:
			// tricky: we should catch WA_ACTIVE and WA_CLICKACTIVE,
			// but only if HIWORD(wParam) (fMinimized) == FALSE (0)
			bActivating = (LOWORD(wparam) != WA_INACTIVE) && (HIWORD(wparam) == 0);
			if(pHGE->pD3D && pHGE->bActive != bActivating) pHGE->_FocusChange(bActivating);
			return FALSE;

		case WM_SETCURSOR:
			if(pHGE->bUpdateMouse) {
				if(pHGE->bActive && LOWORD(lparam)==HTCLIENT && pHGE->bHideMouse) SetCursor(NULL);
				else SetCursor(LoadCursor(NULL, IDC_ARROW));
			}
			return FALSE;

		case WM_SYSKEYDOWN:
			if(wparam == VK_F4)
			{
#ifndef HGE_STATEMACHINE
				if(pHGE->procExitFunc && !pHGE->procExitFunc()) return FALSE;
#else
				res = true;
				if(pHGE->procExitFunc && !pHGE->procExitFunc()) res = false;
				pHGE->Apps->End();
				a = (HGEAPP*)pHGE->Apps->Prev();
				while(a) {
					res = res && a->Exit();
					a = (HGEAPP*)pHGE->Apps->Prev();
				}
				if(!res) return FALSE;
#endif
				pHGE->isExit = true;
				return DefWindowProc(hwnd, msg, wparam, lparam);
			}
			else if(wparam == VK_RETURN)
			{
				pHGE->System_SetState(HGE_WINDOWED, !pHGE->System_GetState(HGE_WINDOWED));
				return FALSE;
			}
			else
			{
				pHGE->_BuildEvent(INPUT_KEYDOWN, wparam, HIWORD(lparam) & 0xFF, (lparam & 0x40000000) ? HGEINP_REPEAT:0, -1, -1);
				return FALSE;
			}

		case WM_KEYDOWN:
			pHGE->_BuildEvent(INPUT_KEYDOWN, wparam, HIWORD(lparam) & 0xFF, (lparam & 0x40000000) ? HGEINP_REPEAT:0, -1, -1);
			return FALSE;
		case WM_SYSKEYUP:
			pHGE->_BuildEvent(INPUT_KEYUP, wparam, HIWORD(lparam) & 0xFF, 0, -1, -1);
			return FALSE;
		case WM_KEYUP:
			pHGE->_BuildEvent(INPUT_KEYUP, wparam, HIWORD(lparam) & 0xFF, 0, -1, -1);
			return FALSE;

		case WM_LBUTTONDOWN:
			SetFocus(hwnd);
			pHGE->_BuildEvent(INPUT_MBUTTONDOWN, HGEK_LBUTTON, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;
		case WM_MBUTTONDOWN:
			SetFocus(hwnd);
			pHGE->_BuildEvent(INPUT_MBUTTONDOWN, HGEK_MBUTTON, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;
		case WM_RBUTTONDOWN:
			SetFocus(hwnd);
			pHGE->_BuildEvent(INPUT_MBUTTONDOWN, HGEK_RBUTTON, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;

		case WM_LBUTTONDBLCLK:
			pHGE->_BuildEvent(INPUT_MBUTTONDOWN, HGEK_LBUTTON, 0, HGEINP_REPEAT, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;
		case WM_MBUTTONDBLCLK:
			pHGE->_BuildEvent(INPUT_MBUTTONDOWN, HGEK_MBUTTON, 0, HGEINP_REPEAT, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;
		case WM_RBUTTONDBLCLK:
			pHGE->_BuildEvent(INPUT_MBUTTONDOWN, HGEK_RBUTTON, 0, HGEINP_REPEAT, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;

		case WM_LBUTTONUP:
			pHGE->_BuildEvent(INPUT_MBUTTONUP, HGEK_LBUTTON, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;
		case WM_MBUTTONUP:
			pHGE->_BuildEvent(INPUT_MBUTTONUP, HGEK_MBUTTON, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;
		case WM_RBUTTONUP:
			pHGE->_BuildEvent(INPUT_MBUTTONUP, HGEK_RBUTTON, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;

		case WM_MOUSEMOVE:
			pHGE->_BuildEvent(INPUT_MOUSEMOVE, 0, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;
		case 0x020A: // WM_MOUSEWHEEL, GET_WHEEL_DELTA_WPARAM(wparam);
			pHGE->_BuildEvent(INPUT_MOUSEWHEEL, short(HIWORD(wparam))/120, 0, 0, LOWORDINT(lparam), HIWORDINT(lparam));
			return FALSE;

		case WM_SIZE:
			if(pHGE->pD3D && wparam==SIZE_RESTORED) pHGE->_Resize(LOWORD(lparam), HIWORD(lparam));
			//return FALSE;
			break;

		case WM_SYSCOMMAND:
			if(wparam==SC_CLOSE)
			{
#ifndef HGE_STATEMACHINE
				if(pHGE->procExitFunc && !pHGE->procExitFunc()) return FALSE;
#else
				res = true;
				if(pHGE->procExitFunc && !pHGE->procExitFunc()) res = false;
				pHGE->Apps->End();
				a = (HGEAPP*)pHGE->Apps->Prev();
				while(a) {
					res = res && a->Exit();
					a = (HGEAPP*)pHGE->Apps->Prev();
				}
				if(!res) return FALSE;
#endif
				pHGE->bActive=false;
				return DefWindowProc(hwnd, msg, wparam, lparam);
			}
			break;
	}

	return DefWindowProc(hwnd, msg, wparam, lparam);
}

